﻿/*
** ActionScript - Gettext External Library: GettextReader class
** Author: Molay
** Version: 0.1
** Modified: 2008-04-27
** Copyright: WebStudio, Inc.
** URL: http://www.molayc.com/		Email: edwardtsds@126.com
**
** Warning: The plural form is still sealed for Molay.
**          It means the ngettext function is experimental, provisionally.
**          If you have some advice, please tell me, thx *_*
*/
package {
	import flash.utils.*;


	public class GettextReader {
		
		public static const magicLittle:uint = 0x950412DE;
		public static const magicBig:uint = 0xDE120495;
		public static const charset:String = "utf-8";
		
		// cache all strings or not.
		private var cacheEnabled:Boolean;
		// if no data, return string self.
		private var shortCircuit:Boolean;
		// the mo binary array.
		private var data:ByteArray;
		// file format revision.
		private var revision:uint;
		// number of strings.
		private var total:uint;
		// original table offset.
		private var originalOffset:uint;
		// translation table offset.
		private var translationOffset:uint;
		// original table.
		private var originalTable:Array;
		// translation table.
		private var translationTable:Array;
		// cache object.
		private var cache:Object;
		// cache header field for plural forms
		private var pluralHeader:String;
		
		
		// Constructor
		public function GettextReader(byteArr:ByteArray, useCache:Boolean = true) {
			if(byteArr==null||byteArr.length<1) {
				shortCircuit = true;
				return;
			}
			
			data = byteArr;
			cacheEnabled = useCache;
			
			// check big-endian or little-endian.
			data.endian = Endian.LITTLE_ENDIAN;
			var magic:uint = data.readUnsignedInt();
			if(magic==magicBig){
				// big-endian.
				data.endian = Endian.BIG_ENDIAN;
			}else if(magic!=magicLittle){
				// not MO file.
				throw new Error("It's not a MO file!");
			}
			
			revision = data.readUnsignedInt();
			total = data.readUnsignedInt();
			originalOffset = data.readUnsignedInt();
			translationOffset = data.readUnsignedInt();
			
			initTables();
		}
		
		// init all tables.
		private function initTables() {
			originalTable=[], translationTable=[];
			var i;
			// seek all original string descriptors.
			data.position = originalOffset;
			for (i=0;i<total;i++){
				originalTable[i]={};
				originalTable[i].length = data.readUnsignedInt();
				originalTable[i].offset = data.readUnsignedInt();
			}
			// seek all translation string descriptors.
			data.position = translationOffset;
			for (i=0;i<total;i++){
				translationTable[i]={};
				translationTable[i].length = data.readUnsignedInt();
				translationTable[i].offset = data.readUnsignedInt();
			}
			// use cache
			if(cacheEnabled){
				cache = {};
				var o, t;
				for (i=0;i<total;i++){
					data.position = originalTable[i].offset;
					o = data.readMultiByte(originalTable[i].length, charset);
					data.position = translationTable[i].offset;
					t = data.readMultiByte(translationTable[i].length, charset);
					cache[o] = t;
				}
			}
		}
		
		// get a string from the original table
		private function getOriginalString(num:uint) {
			if(originalTable[num].offset==undefined) return "";
			data.position = originalTable[num].offset;
			return data.readMultiByte(originalTable[num].length, charset);
		}
		
		// get a string from the translation table
		private function getTranslationString(num:uint) {
			if(translationTable[num].offset==undefined) return "";
			data.position = translationTable[num].offset;
			return data.readMultiByte(translationTable[num].length, charset);
		}
		
		// binary search for string
		private function findString(str:String, start:int = -1, end:int = -1) {
			var temp1, temp2;
			if ((start == -1) || (end == -1)) {
				// find_string is called with only one parameter, set start end end
				start = 0;
				end = total;
			}
			if (Math.abs(start - end) <= 1) {
				temp1 = getOriginalString(start);
				if(str == temp1) return start;
				else return -1;
			} else if (start > end) {
				// reverse
				return findString(str, end, start);
			} else {
				// clamp
				temp1 = uint((start + end) / 2);
				temp2 = str.localeCompare(getOriginalString(temp1));
				if (temp2 == 0) {
					return temp1;
				} else if (temp2 < 0) {
					return findString(str, start, temp1);
				} else {
					return findString(str, temp1, end);
				}
			}
		}
		
		// translates a string
		public function translate(str:String) {
			if(shortCircuit) return str;
			if(cacheEnabled){
				if(cache.hasOwnProperty(str)) return cache[str];
				else return str;
			}else{
				var num = findString(str);
				if (num == -1) return str;
				else return getTranslationString(num);
			}
		}
		
		// get possible plural forms from MO header
		private function getPluralForms() {
			var header, regs, expr, res, p, ch, i;
			if(pluralHeader==null){
				if(cacheEnabled){
					header = cache[""];
				}else{
					header = getTranslationString(0);
				}
				header+= "\n";
				regs = header.match(/plural-forms: ([^\n]*)\n/i);
				if(regs!=null){
					expr = regs[1];
				}else{
					expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
				}
				expr+= ";";
				res = "";
				p = 0;
				expr = expr.split("");
				for(i=0;i<expr.length;i++) {
					ch = expr[i];
					switch (ch) {
						case "?":
							res+= " ? (";
							p++;
							break;
						case ":":
							res+= ") : (";
							break;
						case ";":
							res+= strRepeat(")", p)+";";
							p = 0;
							break;
						default:
							res+= ch;
					}
				}
				pluralHeader = res;
			}
			return pluralHeader;
		}
		
		// repeat a string
		private function strRepeat(str:String, multiplier:uint) {
			var temp = "";
			while(multiplier>0){
				temp += str;
				multiplier--;
			}
			return temp;
		}
		
		// detects which plural form to take
		private function selectString() {
		}
		
		// plural version of gettext
		public function ngettext(single, plural, number) {
			return single;
		}
		
		
		// debug
		public function debug() {
			if(shortCircuit) return;
			for(var i in cache){
				trace(i+":"+cache[i]);
				trace("");
			}
		}
		
	}
}